package net.onrc.onos.api.rest; import net.floodlightcontroller.restserver.RestletRoutable; import org.restlet.Application; import org.restlet.Component; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.Server; import org.restlet.data.Protocol; import org.restlet.data.Reference; import org.restlet.data.Status; import org.restlet.ext.jackson.JacksonRepresentation; import org.restlet.representation.Representation; import org.restlet.routing.Filter; import org.restlet.routing.Router; import org.restlet.routing.Template; import org.restlet.service.StatusService; import java.util.List; /** * A REST API server suitible for inclusion in unit tests. Unit tests can * create a server on a given port, then specify the RestletRoutable classes * that are to be tested. The lifecyle for the server is to create it * and then start it during the @Before (setUp) portion of the test and to * shut it down during the @After (tearDown) section. */ public class TestRestApiServer { private List<RestletRoutable> restlets; private RestApplication restApplication; private Server server; private Component component; /** * The restlet engine requires an Application as a container. */ private class RestApplication extends Application { private final Context context; /** * Initialize the Application along with its Context. */ public RestApplication() { super(); context = new Context(); } /** * Add an attribute to the Context for the Application. This is most * often used to specify attributes that allow modules to locate each * other. * * @param name name of the attribute * @param value value of the attribute */ public void addAttribute(final String name, final Object value) { context.getAttributes().put(name, value); } /** * Sets up the Restlet for the APIs under test using a Router. Also, a * filter is installed to deal with double slashes in URLs. * This code is adapted from * net.floodlightcontroller.restserver.RestApiServer * * @return Router object for the APIs under test. */ @Override public Restlet createInboundRoot() { Router baseRouter = new Router(context); baseRouter.setDefaultMatchingMode(Template.MODE_STARTS_WITH); for (RestletRoutable rr : restlets) { baseRouter.attach(rr.basePath(), rr.getRestlet(context)); } /** * Filter out multiple slashes in URLs to make them a single slash. */ Filter slashFilter = new Filter() { @Override protected int beforeHandle(Request request, Response response) { Reference ref = request.getResourceRef(); String originalPath = ref.getPath(); if (originalPath.contains("//")) { String newPath = originalPath.replaceAll("/+", "/"); ref.setPath(newPath); } return Filter.CONTINUE; } }; slashFilter.setNext(baseRouter); return slashFilter; } /** * Run the Application on an open port. * */ public void run() { try { setStatusService(new StatusService() { @Override public Representation getRepresentation(Status status, Request request, Response response) { return new JacksonRepresentation<>(status); } }); // Start listening for REST requests component = new Component(); server = component.getServers().add(Protocol.HTTP, 0); component.getDefaultHost().attach(this); component.start(); } catch (Exception e) { // Web server did not start. throw new IllegalStateException(e); } } } /** * Start up the REST server. A list of the Restlets being tested is * passed in. The usual use of this method is in the @Before (startUp) * of a JUnit test. * * @param restletsUnderTest list of Restlets to run as part of the server. */ public void startServer(final List<RestletRoutable> restletsUnderTest) { restlets = restletsUnderTest; restApplication = new RestApplication(); restApplication.run(); } /** * Stop the REST server. The container is stopped, and the server will * no longer respond to requests. The usual use of this is in the @After * (tearDown) part of the test. */ public void stopServer() { try { restApplication.stop(); server.stop(); component.stop(); } catch (Exception ex) { // Stopping the server failed, convert to unchecked exception to // abort the calling test with a failure. throw new IllegalStateException(ex); } } /** * Add an attribute to the Context for the Application. This is most * often used to specify attributes that allow modules to locate each * other. * * @param name name of the attribute * @param value value of the attribute */ public void addAttribute(final String name, final Object value) { restApplication.addAttribute(name, value); } /** * Gets the port number being used by the REST web server. * * @return port number */ public int getRestPort() { return server.getActualPort(); } }